package toponet.overlay;

import java.util.Enumeration;
import java.util.Vector;

import sicsim.types.NodeId;
import toponet.geometry.*;
import toponet.overlay.ToponetPeer;

public class FingerTable {
	private Vector <FingerEntry> ft;
	private ToponetPeer proprietor;
	
	public FingerTable() {
		this.ft = new Vector<FingerEntry>();
	}
	
	public FingerTable(ToponetPeer peer) {
		this.ft = new Vector<FingerEntry>();
		this.proprietor = peer;
	}
	
	/*
	 *	Adding an entry to the right position of the finger table
	 *	Need to provide with the new node entry id plus the location 
	 *	TESTED - Small probability of having bugs 
	 *
	 */
	
	public boolean addEntry(NodeId nid, double lon, double lat) {
		if(this.ft.size() == 0) {
			this.ft.add(new FingerEntry(nid, new Point(lon,lat)));
			return true;
		}
		if(this.ftContainsNode(nid)) {
			return false;
		}
		Enumeration<FingerEntry> e = this.ft.elements();
		if(e.hasMoreElements()) {
			FingerEntry first = e.nextElement();
			if(this.ft.size() == 1) {
				if(Orientation.isCounterClockwise(this.proprietor.getLocation().getX(), this.proprietor.getLocation().getY(), first.getPoint().getX(), first.getPoint().getY(), lon, lat)) { 
					this.ft.add(1, new FingerEntry(nid, new Point(lon,lat)));
					return true;
				}
				else {
					this.ft.add(0, new FingerEntry(nid, new Point(lon,lat)));
					return true;
				}
			}
			else {
				int i=1;
				while(e.hasMoreElements()) {
					FingerEntry next = e.nextElement();

					if(Orientation.isCounterClockwise(this.proprietor.getLocation().getX(), this.proprietor.getLocation().getY(), first.getPoint().getX(), first.getPoint().getY(), lon, lat) && !Orientation.isCounterClockwise(this.proprietor.getLocation().getX(), this.proprietor.getLocation().getY(), next.getPoint().getX(), next.getPoint().getY(), lon,lat)) {
						this.ft.add(i, new FingerEntry(nid, new Point(lon,lat)));
						return true;
					}
					first = next;
					i++;
				}
				//if((i== this.ft.size()) && !Orientation.isCounterClockwise(this.proprietor.getLocation().getX(), this.proprietor.getLocation().getY(), this.ft.elementAt(this.ft.size()-1).getPoint().getX(), this.ft.elementAt(this.ft.size()-1).getPoint().getY(), lon, lat) && Orientation.isCounterClockwise(this.proprietor.getLocation().getX(), this.proprietor.getLocation().getY(), this.ft.elementAt(0).getPoint().getX(), this.ft.elementAt(0).getPoint().getY(), lon,lat)) {
				if(i==this.ft.size()) {
					System.out.println("FINAL ELEMENT");
					this.ft.add(new FingerEntry(nid, new Point(lon,lat)));
					return true;
				}
			}
		}
		else {
			this.ft.add(0, new FingerEntry(nid, new Point(lat,lon)));
			return true;
		}
		return false;
	}
	
	public boolean addEntry(NodeId nid, Point p) {
		return this.addEntry(nid, p.getX(), p.getY());
	}
	
	public void print() {
		System.out.println("---PRINT FT---");
		System.out.println("Size = "+this.ft.size());
		Enumeration<FingerEntry> e = this.ft.elements();
		while(e.hasMoreElements()) {
			Point p = e.nextElement().getPoint();
			System.out.println(p.getX()+" - "+p.getY());
		}
	}
	
	public void deleteEntry(NodeId nid) {
		System.out.println("Delete entry");
		System.out.println("Another delete.");
		//TODO
	}
	
	/*
	 * Checks whether the product of the vector created by the initial point and (x1,y1) with the vector created by the initial point and (x2,y2)
	 */

	private boolean isCounterClockwise(double x1, double y1, double x2, double y2) {
		double m[][] = new double[3][3];
		m[0][0] = 1; m[1][0] = this.proprietor.getLocation().getX(); m[2][0] = this.proprietor.getLocation().getY();
		m[0][1] = 1; m[1][1] = x1; m[2][1] = y1;
		m[0][2] = 1; m[1][2] = x2; m[2][2] = y2;
		double value = m[0][0]*m[1][1]*m[2][2]-m[0][2]*m[1][1]*m[2][0]+
						+m[0][1]*m[1][2]*m[2][0]-m[0][1]*m[1][0]*m[2][2]+
						+m[0][2]*m[1][0]*m[2][1]-m[0][0]*m[1][2]*m[2][1];
		if(value>=0)
			return true;
		return false;
	}
	
	public Vector<FingerEntry> getFingerEntries() {
		return this.ft;
	}
	
	public boolean checkFingerIntegrity() {
		FingerEntry first = this.ft.elementAt(this.ft.size()-1);
		Enumeration<FingerEntry> e = this.ft.elements();
		while(e.hasMoreElements()) {					
			FingerEntry next = e.nextElement();
			System.out.println("["+first.getNodeId()+"] - ["+next.getNodeId()+"] "+this.isCounterClockwise(first.getPoint().getX(), first.getPoint().getY(), next.getPoint().getX(), next.getPoint().getY()));
			first = next;
		}
		return false;
	}

	/*
	 * AS LONG AS THERE ARE NO DELETES THERE IS NO PROBLEM
	 * IF DELETES MIGHT HAPPEN A PROBLEM MIGHT OCCUR. 
	 * Need to check orientation in order to form triangles. 
	 * The problem exists only if a node is in the Convex Hull; 
	 */

	public Triangle[] getTriangles() { 		
		//if there are less than 2 entries, no triangles can be formed, return null.
		if (ft.size() < 2)
			return null;
		Triangle[] list;
		
		if(this.proprietor.inConvexHull()) 
			list = new Triangle[ft.size()-1]; 
		else 
			list = new Triangle[ft.size()]; 
		
		if (ft.size() == 2){ 
			list[0] = new Triangle(this.proprietor.getLocation(), ft.get(0).getPoint(), ft.get(1).getPoint());
			return list;
		}
		
		//otherwise, we can form FT size minus one triangles.
		for (int i = 0; i < ft.size()-1; i++) { 
			list[i] = new Triangle(this.proprietor.getLocation(), ft.get(i).getPoint(), ft.get(i+1).getPoint());
		}
		if(!this.proprietor.inConvexHull()) 
			list[ft.size()-1] = new Triangle(this.proprietor.getLocation(), ft.get(ft.size()-1).getPoint(), ft.get(0).getPoint());
		return list;
	}
	
	public NodeId[] getVertice(int index) { 
		NodeId[] id = new NodeId[2];
		
		id[0] = this.getNodeId(index);
		id[1] = this.getNodeId(index+1);
		
		return id; 
	}
	
	public NodeId getClosest(Point p) { 
		
		if (this.ft.size() == 0) 
			return null; 
		
		double min = p.euclideanDist(this.getPoint(0)); 
		NodeId minId = this.ft.get(0).getNodeId();
		
		for (int i = 1; i < this.ft.size(); i++) { 
			double temp = p.euclideanDist(this.getPoint(i));
			
			if (temp < min) {
				min = temp; 
				minId = this.getNodeId(i);
			}
		}
		
		return minId;
	}
	
	
	public int size() {
		return this.ft.size();
	}
	
	//maybe we won't need this
	public Point getPoint(int index) { 
		return this.ft.get(index).getPoint();
	}
	
	//maybe we won't need this
	public NodeId getNodeId(int index) { 
		if (index >= this.ft.size())
			System.out.println("WHATHAFACK");
		return this.ft.get(index).getNodeId();
	}
	
	private boolean ftContainsNode(NodeId nid) {
		Enumeration<FingerEntry> e = this.ft.elements();
		while(e.hasMoreElements()) {
			FingerEntry fe = e.nextElement();
			if(fe.getNodeId().equals(nid))
				return true;
		}
		return false;
	}
}

